package biz

import (
	"context"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/transport"
	"github.com/go-kratos/kratos/v2/transport/http"
	redis3 "github.com/go-redis/redis"
	"net/url"
	v1 "gitlab.cqcb.com/shangyou_mic/dscm-approve-api-pb/api/roses/v1"
	"rosesbff/internal/conf"
	"rosesbff/internal/data/redis"
	"sort"
	"strings"
	"sync"
	"time"
)

// separate

type Separate struct {
	*Basic
	*SystemConfig
	aliYunOssCredentialsSecurityToken sync.Mutex
	cfg                               *conf.Bootstrap
	redis                             *redis.Redis
	logger                            log.Logger

	// stsCache struct {
	// 	sync.RWMutex
	// 	data map[string]string // key: AccessKeyID, value: AccessKeySecret
	// }
}

func NewSeparate(
	cfg *conf.Bootstrap,
	basic *Basic,
	redis *redis.Redis,
	logger log.Logger,
) (*Separate, error) {
	result := &Separate{
		cfg:    cfg,
		redis:  redis,
		logger: logger,
	}
	result.Basic = basic
	// result.stsCache.data = make(map[string]string)
	return result, nil
}

func (s *Separate) CredentialsSecurityToken(ctx context.Context, req *v1.Empty) (*v1.CredentialsSecurityTokenReply, error) {
	reply := &v1.CredentialsSecurityTokenReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	reply.Data = &v1.CredentialsSecurityTokenReply_Data{}
	key := "roses:aly:oss:credentials_security_token"
	client := s.redis.Client()
	cmd := client.Get(key)
	if err := cmd.Err(); err != nil {
		if !errors.Is(redis3.Nil, err) {
			return nil, err
		}
	} else {
		if value := cmd.Val(); value != "" {
			if err = json.Unmarshal([]byte(cmd.Val()), reply.Data); err != nil {
				return nil, err
			}
			return reply, nil
		}
	}
	s.aliYunOssCredentialsSecurityToken.Lock()
	defer s.aliYunOssCredentialsSecurityToken.Unlock()
	seconds := int64(3600)
	token, err := s.Basic.aliYunCredentialsSecurityToken(seconds)
	if err != nil {
		return nil, err
	}
	reply.Data = &v1.CredentialsSecurityTokenReply_Data{
		Region:                   s.cfg.AliYunOss.Region,
		AccessKeyId:              s.cfg.AliYunOss.AccessKeyId,
		AccessKeySecret:          s.cfg.AliYunOss.AccessKeySecret,
		Bucket:                   s.cfg.AliYunOss.Bucket,
		CredentialsSecurityToken: token,
	}
	bts, err := json.Marshal(reply.Data)
	if err != nil {
		return nil, err
	}
	if err = client.Set(key, string(bts), time.Second*time.Duration(seconds-30)).Err(); err != nil {
		return nil, err
	}
	return reply, nil
}

// GetUploadCredentials 实现获取上传凭证接口
func (s *Separate) GetUploadCredentials(ctx context.Context, req *v1.GetUploadCredentialsRequest) (*v1.GetUploadCredentialsResponse, error) {
	req.UserId = s.GetUserId(ctx)
	// 1. 获取STS临时凭证
	stsResp, err := s.getSTSToken()
	if err != nil {
		// s.logger.Errorf("获取STS凭证失败: %v", err)
		return nil, fmt.Errorf("获取上传凭证失败: %w", err)
	}

	// 2. 缓存STS凭证（用于回调验证）
	// s.stsCache.Lock()
	// s.stsCache.data[stsResp.AccessKeyID] = stsResp.AccessKeySecret
	// s.stsCache.Unlock()

	// 3. 生成上传参数（含回调配置）
	uploadParams, err := s.generateUploadParams(stsResp, req.GetUserId())
	if err != nil {
		// s.logger.Errorf("生成上传参数失败: %v", err)
		return nil, fmt.Errorf("生成上传参数失败: %w", err)
	}

	return &v1.GetUploadCredentialsResponse{
		StatusCode: 200,
		Message:    "success",
		Data: &v1.GetUploadCredentialsResponse_Data{
			AccessKeyId:     uploadParams["accessKeyId"],
			AccessKeySecret: uploadParams["accessKeySecret"],
			SecurityToken:   uploadParams["securityToken"],
			Policy:          uploadParams["policy"],
			Signature:       uploadParams["signature"],
			Callback:        uploadParams["callback"],
			Host:            uploadParams["host"],
			Region:          s.cfg.AliYunOss.Region,
			Bucket:          s.cfg.AliYunOss.Bucket,
		},
	}, nil
}

// OSSCallback 实现OSS回调接口
func (s *Separate) OSSCallback(ctx context.Context, req *v1.OSSCallbackRequest) (*v1.OSSCallbackResponse, error) {
	// 从请求头中获取OSS回调签名信息
	httpTemp, ok := transport.FromServerContext(ctx)
	if !ok {
		return nil, nil
	}
	httpCtx := httpTemp.(*http.Transport)
	ossAK := httpCtx.Request().Header.Get("x-oss-access-key-id")
	authorization := httpCtx.Request().Header.Get("Authorization")

	// 1. 验证回调签名
	if !s.verifyCallback(httpCtx.Request(), ossAK, authorization) {
		// s.logger.Warnf("回调签名验证失败，AK: %s", ossAK)
		return &v1.OSSCallbackResponse{
			StatusCode: 403,
			Message:    "回调签名验证失败",
		}, nil
	}

	// 2. 业务逻辑处理（示例：生成文件URL）
	fileURL := fmt.Sprintf("https://%s.%s/%s",
		s.cfg.AliYunOss.Bucket,
		s.cfg.AliYunOss.Endpoint,
		req.Filename,
	)
	// s.logger.Infof("用户 %s 上传文件成功，URL: %s", req.UserId, fileURL)

	// 3. 保存文件信息到数据库（示例）
	// if err := s.saveFileInfo(req.UserId, fileURL, req.Size, req.Mime_type); err != nil {
	//     s.logger.Errorf("保存文件信息失败: %v", err)
	//     return &v1.OSSCallbackResponse{
	//         Code:    500,
	//         Message: "保存文件信息失败",
	//     }, nil
	// }

	return &v1.OSSCallbackResponse{
		StatusCode: 200,
		Message:    "回调处理成功",
		Data: map[string]string{
			"file_url": fileURL,
		},
	}, nil
}

// -------------------------- 内部工具方法 --------------------------

// getSTSToken 获取阿里云STS临时凭证
func (s *Separate) getSTSToken() (*stsCredentials, error) {
	client, err := sts.NewClientWithAccessKey(
		s.cfg.AliYunOss.Region,
		s.cfg.AliYunOss.AccessKeyId,
		s.cfg.AliYunOss.AccessKeySecret,
	)
	if err != nil {
		return nil, fmt.Errorf("创建STS客户端失败: %w", err)
	}

	request := sts.CreateAssumeRoleRequest()
	request.Scheme = "https"
	request.RoleArn = s.cfg.AliYunOss.RoleArn
	request.RoleSessionName = s.cfg.AliYunOss.RoleSessionName
	request.DurationSeconds = requests.NewInteger(3600)

	response, err := client.AssumeRole(request)
	if err != nil {
		return nil, fmt.Errorf("调用STS接口失败: %w", err)
	}

	return &stsCredentials{
		AccessKeyID:     response.Credentials.AccessKeyId,
		AccessKeySecret: response.Credentials.AccessKeySecret,
		SecurityToken:   response.Credentials.SecurityToken,
		Expiration:      response.Credentials.Expiration,
	}, nil
}

// generateUploadParams 生成前端上传所需参数（含回调配置）
func (s *Separate) generateUploadParams(stsResp *stsCredentials, userId int64) (map[string]string, error) {
	// 1. 构造回调参数
	callback := callbackParam{
		// CallbackURL:      s.aliConf.CallbackUrl,
		CallbackURL:      s.cfg.AliYunOss.CallbackUrl,
		CallbackBody:     fmt.Sprintf(`{"filename":"${object}","size":${size},"mimeType":"${mimeType}","userId":"%d"}`, userId),
		CallbackBodyType: "application/json",
	}
	callbackJSON, err := json.Marshal(callback)
	if err != nil {
		return nil, fmt.Errorf("序列化回调参数失败: %w", err)
	}
	callbackBase64 := base64.StdEncoding.EncodeToString(callbackJSON)

	// 2. 构造上传策略（Policy）
	expiration := time.Now().Add(time.Second * 3600).UTC().Format(time.RFC3339)
	policy := uploadPolicy{
		Expiration: expiration,
		Conditions: []interface{}{
			map[string]string{"bucket": s.cfg.AliYunOss.Bucket},
			[]string{"starts-with", "$key", "uploads/"},
			[]any{"content-length-range", 0, 104857600},
			// map[string]string{"action": "oss:PutObject"},
			// map[string]string{"x-oss-meta-userid": fmt.Sprintf("%d", userId)}, // 限制仅允许当前用户ID
		},
	}
	policyJSON, err := json.Marshal(policy)
	if err != nil {
		return nil, fmt.Errorf("序列化Policy失败: %w", err)
	}
	policyBase64 := base64.StdEncoding.EncodeToString(policyJSON)

	// 3. 计算签名
	signature := s.calculateSignature(stsResp.AccessKeySecret, policyBase64)

	return map[string]string{
		"accessKeyId":     stsResp.AccessKeyID,
		"accessKeySecret": stsResp.AccessKeySecret,
		"securityToken":   stsResp.SecurityToken,
		"policy":          policyBase64,
		"signature":       signature,
		"callback":        callbackBase64,
		"host":            fmt.Sprintf("https://%s.oss-%s", s.cfg.AliYunOss.Bucket, s.cfg.AliYunOss.Endpoint),
	}, nil
}

// verifyCallback 验证OSS回调签名合法性
func (s *Separate) verifyCallback(req *http.Request, ossAK, authorization string) bool {
	// 1. 获取缓存的STS Secret
	// s.stsCache.RLock()
	// stsSecret, ok := s.stsCache.data[ossAK]
	// s.stsCache.RUnlock()
	// if !ok {
	// 	return false
	// }

	// 2. 解析回调URL获取路径
	parsedURL, err := url.Parse(s.cfg.AliYunOss.CallbackUrl)
	if err != nil {
		return false
	}
	path := parsedURL.Path

	// 3. 处理OSS自定义Header（x-oss-开头）
	var ossHeaders []string
	ossHeaderMap := make(map[string]string)
	for k, v := range req.Header {
		lowerK := strings.ToLower(k)
		if strings.HasPrefix(lowerK, "x-oss-") {
			ossHeaders = append(ossHeaders, lowerK)
			ossHeaderMap[lowerK] = strings.Join(v, ",")
		}
	}
	sort.Strings(ossHeaders)

	// 4. 拼接CanonicalizedOSSHeaders
	var canonicalizedOSSHeaders string
	for _, k := range ossHeaders {
		canonicalizedOSSHeaders += fmt.Sprintf("%s:%s\n", k, ossHeaderMap[k])
	}

	// 5. 拼接待签名字符串（OSS规定格式）
	stringToSign := fmt.Sprintf(
		"%s\n%s\n%s\n%s\n%s%s",
		req.Method,
		req.Header.Get("Content-MD5"),
		req.Header.Get("Content-Type"),
		req.Header.Get("Date"),
		canonicalizedOSSHeaders,
		path,
	)

	// 6. 计算签名并对比
	stsSecret := s.cfg.AliYunOss.AccessKeySecret
	h := hmac.New(sha1.New, []byte(stsSecret))
	h.Write([]byte(stringToSign))
	expectedSign := base64.StdEncoding.EncodeToString(h.Sum(nil))
	expectedAuth := fmt.Sprintf("OSS %s:%s", ossAK, expectedSign)

	return expectedAuth == authorization
}

// calculateSignature 计算Policy签名（HMAC-SHA1）
func (s *Separate) calculateSignature(secret, policyBase64 string) string {
	h := hmac.New(sha1.New, []byte(secret))
	h.Write([]byte(policyBase64))
	return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

// -------------------------- 内部结构体 --------------------------
type stsCredentials struct {
	AccessKeyID     string
	AccessKeySecret string
	SecurityToken   string
	Expiration      string
}

type callbackParam struct {
	CallbackURL      string `json:"callbackUrl"`
	CallbackBody     string `json:"callbackBody"`
	CallbackBodyType string `json:"callbackBodyType"`
}

type uploadPolicy struct {
	Expiration string        `json:"expiration"`
	Conditions []interface{} `json:"conditions"`
}
